package org.springframework.security.authentication;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.log.LogMessage;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author Dillon
 * @date 2024/6/26
 * @slogan 致敬大师 致敬未来的你
 * @desc 认证管理器默认实现类，
 * 管理众多的 AuthenticationProvider 实例，该实例类似于 AuthenticationManager 接口，只不过多了一个是否支持认证的接口方法
 */
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

	/**
	 * 日志对象
	 */
	private static final Log logger = LogFactory.getLog(ProviderManager.class);

	/**
	 * 事件发布器 默认不发布事件
	 */
	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();

	/**
	 * 认证提供器列表，同一个认证请求可以存在多种认证方式
	 */
	private List<AuthenticationProvider> providers = Collections.emptyList();

	/**
	 * 国际化消息决策器
	 */
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	/**
	 * 父类认证器 默认无
	 */
	private AuthenticationManager parent;

	/**
	 * 认证成功后是否擦除密码 默认擦除
	 */
	private boolean eraseCredentialsAfterAuthentication = true;

	/**
	 * 构建函数
	 *
	 * @param providers 认证实现接口数组
	 */
	public ProviderManager(AuthenticationProvider... providers) {
		this(Arrays.asList(providers), null);
	}

	/**
	 * 构造函数
	 *
	 * @param providers 认证实现接口列表
	 */
	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, null);
	}

	/**
	 * 构造函数
	 *
	 * @param providers 认证实现接口列表
	 * @param parent    默认父类认证接口
	 */
	public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
		Assert.notNull(providers, "认证实现列表不能为空");
		this.providers = providers;
		this.parent = parent;
		checkState();
	}

	/**
	 * 后验方法
	 */
	@Override
	public void afterPropertiesSet() {
		checkState();
	}

	/**
	 * 认证器校验
	 */
	private void checkState() {
		Assert.isTrue(this.parent != null || !this.providers.isEmpty(), "认证实现或者父类认证至少存在一个");
		Assert.isTrue(!CollectionUtils.contains(this.providers.iterator(), null), "认证器列表中不能包含null");
	}

	/**
	 * 认证接口，该接口有三个不同返回值表示认证状态
	 * 1、返回 Authentication 表示认证成功
	 * 2、抛出 AuthenticationException 表示认证失败
	 * 3、返回null 表示不能断定认证
	 *
	 * @param authentication 认证实现类
	 * @return 是否认证成功
	 * @throws AuthenticationException 认证失败异常
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// 获取认证接口类
		Class<? extends Authentication> toTest = authentication.getClass();
		// 域引用设置
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		// 循环认证实现
		for (AuthenticationProvider provider : getProviders()) {
			// 如果认证实现不支持，执行下一个认证
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("请求认证开始，通过 %s (%d/%d)认证", provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				// 尝试认证 如果成功则会返回填充好的认证
				result = provider.authenticate(authentication);
				// 认证成功，复制属性，跳出循环
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			} catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				// 账号认证异常 准备异常消息并抛出异常
				// 准备异常消息就是发布异常事件
				prepareException(ex, authentication);
				throw ex;
			} catch (AuthenticationException ex) {
				// 其他认证异常，记录异常
				lastException = ex;
			}
		}
		// 如果正常认证未通过，且父类存在，则尝试父类认证
		if (result == null && this.parent != null) {
			try {
				// 父类尝试认证
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			} catch (ProviderNotFoundException ex) {
				// 不做处理，认为认证失败 这里对象下面如果 lastException == null 逻辑
			} catch (AuthenticationException ex) {
				// 其他认证异常 异常记录
				parentException = ex;
				lastException = ex;
			}
		}
		// 认证成功逻辑
		if (result != null) {
			// 如果设置认证成功且认证管理器实现 擦除凭证接口，则调用擦除凭证方法
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				((CredentialsContainer) result).eraseCredentials();
			}
			// 父类认证成功不发布认证成功事件，只有认证列表实现类中认证成功，才发布认证成功事件
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// 认证失败逻辑 如果单纯认证失败 初始化一个未找到合适认证器异常
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
		}
		// 父类认证失败不发布认证失败事件，只有认证列表实现类中认证失败，才发布认证失败事件
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		// 抛出认证异常
		throw lastException;
	}

	/**
	 * 发布认证失败事件
	 *
	 * @param ex   认证异常
	 * @param auth 认证实现
	 */
	@SuppressWarnings("deprecation")
	private void prepareException(AuthenticationException ex, Authentication auth) {
		this.eventPublisher.publishAuthenticationFailure(ex, auth);
	}

	/**
	 * dest 中不存在 details时 才进行赋值
	 *
	 * @param source 原对象
	 * @param dest   目标对象
	 */
	private void copyDetails(Authentication source, Authentication dest) {
		if ((dest instanceof AbstractAuthenticationToken token) && (dest.getDetails() == null)) {
			token.setDetails(source.getDetails());
		}
	}

	/**
	 * 获取认证实现列表
	 *
	 * @return 认证实现列表
	 */
	public List<AuthenticationProvider> getProviders() {
		return this.providers;
	}

	/**
	 * 国际消息实现类设置
	 *
	 * @param messageSource 国际消息实现类
	 */
	@Override
	public void setMessageSource(@NotNull MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	/**
	 * 事件发布器设置
	 *
	 * @param eventPublisher 事假发布器
	 */
	public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
		Assert.notNull(eventPublisher, "事件发布器不能为空");
		this.eventPublisher = eventPublisher;
	}

	/**
	 * 设置认证成功后是否擦除凭证 默认擦除
	 *
	 * @param eraseSecretData 设置值
	 */
	public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
		this.eraseCredentialsAfterAuthentication = eraseSecretData;
	}

	/**
	 * 获取认证成功后是否擦除凭证
	 *
	 * @return 配置值
	 */
	public boolean isEraseCredentialsAfterAuthentication() {
		return this.eraseCredentialsAfterAuthentication;
	}

	/**
	 * 默认事件发布器，不发布事件
	 */
	private static final class NullEventPublisher implements AuthenticationEventPublisher {

		@Override
		public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
		}

		@Override
		public void publishAuthenticationSuccess(Authentication authentication) {
		}

	}

}
